/* md5.c : md5 
 * Copyright (C) 2005-2012, drangon <drangon.zhou@gmail.com>
 * 2005-04
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/md5.h>
#include <dgnsc/base64.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct dgn_md5_st
{
	uint32_t mb[4]; // A B C D
	int64_t total_len; // bits
	int32_t last_len; // last buffer not process, < 64
	char last_buf[64];
};

static void process_data( dgn_md5_t md5, const char * data );

dgn_md5_t dgn_md5_init()
{
	struct dgn_md5_st * md5 = (struct dgn_md5_st *)malloc( sizeof(struct dgn_md5_st) );
	md5->mb[0] = 0x67452301;
	md5->mb[1] = 0xefcdab89;
	md5->mb[2] = 0x98badcfe;
	md5->mb[3] = 0x10325476;
	md5->total_len = 0;
	md5->last_len = 0;
	memset( md5->last_buf, 0, sizeof(md5->last_buf) );
	
	return md5;
}

void dgn_md5_reset( dgn_md5_t md5 )
{
	md5->mb[0] = 0x67452301;
	md5->mb[1] = 0xefcdab89;
	md5->mb[2] = 0x98badcfe;
	md5->mb[3] = 0x10325476;
	md5->total_len = 0;
	md5->last_len = 0;
	memset( md5->last_buf, 0, sizeof(md5->last_buf) );
	
	return;
}

void dgn_md5_pushdata( dgn_md5_t md5, const char * data, int len )
{
	int curr_pos = 0;

	if( data == NULL && len < 0 )
		return;

	// process last buf first
	if( md5->last_len > 0 ) {
		if( md5->last_len + len < 64 ) {
			memcpy( md5->last_buf + md5->last_len, data, len );
			md5->last_len += len;
			return;
		}
		// process last buf and data;
		memcpy( md5->last_buf + md5->last_len, data, 64 - md5->last_len );
		process_data( md5, md5->last_buf );
		md5->total_len += 512;

		curr_pos = 64 - md5->last_len;
		md5->last_len = 0;
	}
	
	for( ; curr_pos + 64 <= len; curr_pos += 64 ) {
		process_data( md5, data + curr_pos );
		md5->total_len += 512;
	}
	
	md5->last_len = len - curr_pos;
	if( md5->last_len > 0 ) 
		memcpy( md5->last_buf, data + curr_pos, md5->last_len );

	return;
}

uint8_t * dgn_md5_finish( dgn_md5_t md5 )
{
	md5->total_len += md5->last_len * 8;

	if( md5->last_len >= 56 ) {
		md5->last_buf[md5->last_len] = 0x80;
		memset( md5->last_buf + md5->last_len + 1, 0, 64 - md5->last_len - 1 );
		process_data( md5, md5->last_buf );
		memset( md5->last_buf, 0, 56 );
	}
	else {
		md5->last_buf[md5->last_len] = 0x80;
		memset( md5->last_buf + md5->last_len + 1, 0, 56 - md5->last_len - 1 );
	}
	
	memcpy( md5->last_buf + 56, (const char *)&(md5->total_len), 8 );
	process_data( md5, md5->last_buf );

	// all data finished, create message
	return (uint8_t *)md5->mb;
}

uint8_t * dgn_md5_get( dgn_md5_t md5 )
{
	return (uint8_t *)md5->mb;
}

void dgn_md5_getstr( dgn_md5_t md5, char md5str[33] )
{
	dgn_base16_enc( md5str, (char *)md5->mb, 16 );
	md5str[32] = '\0';
	return;
}

void dgn_md5_fini( dgn_md5_t md5 )
{
	if( md5 != NULL ) {
		free( md5 );
	}
	return;
}

// MD5 T value
#define T_MASK ((uint32_t)~0UL)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3    0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6    0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9    0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13    0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16    0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19    0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22    0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25    0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28    0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31    0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35    0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38    0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41    0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44    0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47    0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50    0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53    0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57    0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60    0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63    0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)

static void process_data( dgn_md5_t md5, const char * data )
{
	uint32_t oldmb[4];
	uint32_t * xx = NULL;

	oldmb[0] = md5->mb[0];
	oldmb[1] = md5->mb[1];
	oldmb[2] = md5->mb[2];
	oldmb[3] = md5->mb[3];

	xx = (uint32_t *)data;

	// rotate shift left
#define RSL(x, n) ( ((x) << (n)) | ((x) >> (32 - (n))) )
	
	// Round 1
#define F(x,y,z) ( ((x) & (y)) | ( ~(x) & (z) ) )
#define R1(a,b,c,d,k,s,i) (a) = (b) + RSL( ((a) + F( (b), (c), (d) ) \
				+ (k) + (i) ), (s) )
#define S11	7
#define S12	12
#define S13	17
#define S14	22

	R1( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[0], S11, T1 );
	R1( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[1], S12, T2 );
	R1( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[2], S13, T3 );
	R1( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[3], S14, T4 );

	R1( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[4], S11, T5 );
	R1( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[5], S12, T6 );
	R1( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[6], S13, T7 );
	R1( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[7], S14, T8 );

	R1( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[8], S11, T9 );
	R1( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[9], S12, T10 );
	R1( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[10], S13, T11 );
	R1( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[11], S14, T12 );

	R1( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[12], S11, T13 );
	R1( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[13], S12, T14 );
	R1( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[14], S13, T15 );
	R1( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[15], S14, T16 );

#undef F
#undef R1
#undef S11
#undef S12
#undef S13
#undef S14

	// Round 2
#define G(x,y,z) ( ((x) & (z)) | ( (y) & ~(z) ) )
#define R2(a,b,c,d,k,s,i) (a) = (b) + RSL( ((a) + G( (b), (c), (d) ) \
				+ (k) + (i) ), (s) )
#define S21	5
#define S22	9
#define S23	14
#define S24	20

	R2( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[1], S21, T17 );
	R2( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[6], S22, T18 );
	R2( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[11], S23, T19 );
	R2( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[0], S24, T20 );

	R2( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[5], S21, T21 );
	R2( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[10], S22, T22 );
	R2( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[15], S23, T23 );
	R2( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[4], S24, T24 );

	R2( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[9], S21, T25 );
	R2( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[14], S22, T26 );
	R2( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[3], S23, T27 );
	R2( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[8], S24, T28 );

	R2( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[13], S21, T29 );
	R2( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[2], S22, T30 );
	R2( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[7], S23, T31 );
	R2( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[12], S24, T32 );

#undef G
#undef R2
#undef S21
#undef S22
#undef S23
#undef S24

	// Round 3
#define H(x,y,z) ( (x) ^ (y) ^ (z) )
#define R3(a,b,c,d,k,s,i) (a) = (b) + RSL( ((a) + H( (b), (c), (d) ) \
				+ (k) + (i) ), (s) )
#define S31	4
#define S32	11
#define S33	16
#define S34	23

	R3( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[5], S31, T33 );
	R3( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[8], S32, T34 );
	R3( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[11], S33, T35 );
	R3( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[14], S34, T36 );

	R3( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[1], S31, T37 );
	R3( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[4], S32, T38 );
	R3( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[7], S33, T39 );
	R3( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[10], S34, T40 );

	R3( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[13], S31, T41 );
	R3( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[0], S32, T42 );
	R3( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[3], S33, T43 );
	R3( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[6], S34, T44 );

	R3( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[9], S31, T45 );
	R3( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[12], S32, T46 );
	R3( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[15], S33, T47 );
	R3( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[2], S34, T48 );

#undef H
#undef R3
#undef S31
#undef S32
#undef S33
#undef S34

	// Round 4
#define I(x,y,z) ( (y) ^ ( (x) | ( ~(z) ) ) )
#define R4(a,b,c,d,k,s,i) (a) = (b) + RSL( ((a) + I( (b), (c), (d) ) \
				+ (k) + (i) ), (s) )
#define S41	6
#define S42 10	
#define S43	15
#define S44	21

	R4( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[0], S41, T49 );
	R4( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[7], S42, T50 );
	R4( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[14], S43, T51 );
	R4( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[5], S44, T52 );

	R4( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[12], S41, T53 );
	R4( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[3], S42, T54 );
	R4( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[10], S43, T55 );
	R4( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[1], S44, T56 );

	R4( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[8], S41, T57 );
	R4( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[15], S42, T58 );
	R4( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[6], S43, T59 );
	R4( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[13], S44, T60 );

	R4( md5->mb[0], md5->mb[1], md5->mb[2], md5->mb[3], xx[4], S41, T61 );
	R4( md5->mb[3], md5->mb[0], md5->mb[1], md5->mb[2], xx[11], S42, T62 );
	R4( md5->mb[2], md5->mb[3], md5->mb[0], md5->mb[1], xx[2], S43, T63 );
	R4( md5->mb[1], md5->mb[2], md5->mb[3], md5->mb[0], xx[9], S44, T64 );

#undef I
#undef R4
#undef S41
#undef S42
#undef S43
#undef S44


	// end
	md5->mb[0] += oldmb[0];
	md5->mb[1] += oldmb[1];
	md5->mb[2] += oldmb[2];
	md5->mb[3] += oldmb[3];

	return;
}

